home *** CD-ROM | disk | FTP | other *** search
/ BBS in a Box 11 / AMUG BBS in a Box Volume XI (April 1994) (MacWizards).iso / Files / Hyper / Q-R / RTF XCMDs1.3.sit / RTF-XCMDs1.3 / rtf.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-20  |  15.0 KB  |  654 lines  |  [TEXT/KAHL]

  1. /*
  2.  * This software is copyright 1992 by Robert Morris.
  3.  * You may freely redistribute this software as shareware
  4.  * if you do so in the same form as you got it. If you find
  5.  * this software useful, please send $12 to:
  6.  *   Robert Morris
  7.  *   P.O. Box 1044
  8.  *   Harvard Square Station
  9.  *   Cambridge, MA 02238
  10.  *   ecognome@aol.com
  11.  * If you incorporate any of this software in any kind of
  12.  * commercial product, please send $2 per copy distributed
  13.  * to the above address.
  14.  */
  15.  
  16. /*
  17.  * utilities to help parse RTF.
  18.  */
  19.  
  20. #include <stdlib.h>
  21. #include <ctype.h>
  22. #include <string.h>
  23. #include <HyperXCmd.h>
  24. #include "lists.h"
  25. #include "rtf.h"
  26.  
  27. int sprintf(char *, ...);
  28.  
  29. extern int goterror;
  30. void error(char *);
  31.  
  32. typedef struct state{
  33.     int font;
  34.     int face;
  35.     int size;
  36.     int dirty;    /* do we need a new TE style before next char? */
  37.     int isfonttable;    /* \fonttbl group */
  38.     int deffont;    /* default rtf font # */
  39.     int invisible;
  40.     int caps;
  41.     struct state *last;
  42. } State;
  43. State *pushstate(State *old), *popstate(State *);
  44.  
  45. struct ftable{
  46.     int rtf;    /* rtf font # */
  47.     int mac;    /* mac font # */
  48. } **ftable;
  49.  
  50. int startstyle(MyStyleHandle sh, long start, int font, int face, int size);
  51. void readcontrol(struct list *, char *word, char *arg);
  52. void docontrol(struct list *inp, struct list *outp, State *state,
  53.                MyStyleHandle sh, char control[], char arg[]);
  54. void appchars(struct list *outp, char *s, State *state, MyStyleHandle sh);
  55.  
  56. /*
  57.  * txt should be a handle to RTF text.
  58.  * puts a (partial) style handle into *outsh, and a text handle into *outtxt.
  59.  * if outsh is zero, don't bother with styles.
  60.  * the text handle is null-terminated.
  61.  */
  62. void
  63. parsertf(Handle txt, MyStyleHandle *outsh, Handle *outtxt)
  64. {
  65.     struct list out, in;
  66.     register int c;
  67.     STElement *stp;
  68.     MyStyleHandle sh;
  69.     register State *state;
  70.     long txtlen;
  71.     
  72.     if(outsh)
  73.         *outsh = 0;
  74.     *outtxt = 0;
  75.     out.h = 0;
  76.     state = 0;
  77.     sh = 0;
  78.     ftable = 0;
  79.     
  80.     ftable = (struct ftable **)NewHandle(0L);
  81.     if(ftable == 0){
  82.         error("error : out of memory for ftable");
  83.         goto out;
  84.     }
  85.  
  86.     if(outsh){
  87.         sh = NewMyStyleHandle();
  88.         if(sh == 0){
  89.             error("error : out of memory for sh");
  90.             goto out;
  91.         }
  92.     } else {
  93.         sh = 0;
  94.     }
  95.  
  96.     state = pushstate((State *) 0);
  97.     if(state == 0){
  98.         error("error : out of memory for state");
  99.         goto out;
  100.     }
  101.     
  102.     NewList(txt, &in);
  103.     NewList((char **) 0, &out);
  104.  
  105.     while((c = ReadListChar(&in)) != -1 && c != '\0' && goterror == 0){
  106.         if(c == '\\'){
  107.             char control[256], arg[256];
  108.             readcontrol(&in, control, arg);
  109.             docontrol(&in, &out, state, sh, control, arg);
  110.         } else if(c == '{'){
  111.             state = pushstate(state);
  112.             if(state == 0){
  113.                 error("error : out of memory for new state");
  114.                 goto out;
  115.             }
  116.         } else if(c == '}'){
  117.             state = popstate(state);
  118.             if(state == 0){
  119.                 error("error : unbalanced }");
  120.                 goto out;
  121.             }
  122.             state->dirty = 1;
  123.         } else if(state->isfonttable == 0 &&
  124.                   c != '\r' &&
  125.                   c != '\n' &&
  126.                   state->invisible == 0){
  127.             if(state->dirty){
  128.                 startstyle(sh, out.ptr, state->font, state->face, state->size);
  129.                 state->dirty = 0;
  130.             }
  131.             if(state->caps && islower(c))
  132.                 c = toupper(c);
  133.             _AppendListChar(&out, c);
  134.         }
  135.     }
  136.         
  137.     /* add dummy style to mark the end */
  138.     startstyle(sh, out.ptr, 0, 0, 12);
  139.     
  140.     if(sh)
  141.         (*sh)->nRuns -= 1; /* nRuns doesn't count that last dummy run! */
  142.     
  143.     if(goterror)
  144.         goto out;
  145.     
  146.     TrimList(&out);
  147.     if(out.h == 0){
  148.         error("error : out of memory for output text");
  149.         goto out;
  150.     }
  151.     
  152.     /* eliminate the trailing null */
  153.     txtlen = GetHandleSize(out.h);
  154.     if(txtlen > 0 && (*out.h)[txtlen-1] == '\0')
  155.         SetHandleSize(out.h, txtlen - 1);
  156.         
  157.     *outtxt = out.h;
  158.     out.h = 0;
  159.     if(outsh){
  160.         *outsh = sh;
  161.         sh = 0;
  162.     }
  163.  
  164. out:
  165.     if(ftable)
  166.         DisposHandle((Handle) ftable);
  167.     if(sh)
  168.         DisposMyStyleHandle(sh);
  169.     if(out.h)
  170.         DisposHandle(out.h);
  171.     while(state)
  172.         state = popstate(state);
  173. }
  174.  
  175. int
  176. startstyle(sh, start, font, face, size)
  177. register MyStyleHandle sh;
  178. long start;
  179. int font, face, size;
  180. {
  181.     register int si;
  182.     int run, err;
  183.     register STPtr st;
  184.     STPtr st1;
  185.     long newlen;
  186.     
  187.     if(sh == 0)
  188.         return;
  189.     
  190.     /* is there an existing style like this? */
  191.     st = 0;
  192.     for(si = 0; si < (*sh)->nStyles; si++){
  193.         st = (*((*sh)->styleTab)) + si;
  194.         if(st->stFont == font && st->stFace == face && st->stSize == size)
  195.             break;
  196.     }
  197.     if(si >= (*sh)->nStyles)
  198.         st = 0;
  199.     
  200.     /* is the last style just like this one? */
  201.     if(st && (*sh)->nRuns > 0 &&
  202.        (*sh)->runs[(*sh)->nRuns-1].styleIndex == si)
  203.         return;
  204.     
  205.     if(st == 0){
  206.         (*sh)->nStyles += 1;
  207.         SetHandleSize((Handle)((*sh)->styleTab),
  208.                       (*sh)->nStyles * sizeof(STElement));
  209.         if((err = MemError()) != 0){
  210.             (*sh)->nStyles = 0;
  211.             SetHandleSize((Handle)((*sh)->styleTab), 0L);
  212.             error("error : out of memory for more styleTab");
  213.             return(err);
  214.         }
  215.         si = (*sh)->nStyles - 1;
  216.         st = (*((*sh)->styleTab)) + si;
  217.         st->stCount = 0;
  218.         st->stHeight = 12; /* ??? */
  219.         st->stAscent = 10; /* ??? */
  220.         st->stFont = font;
  221.         st->stFace = face;
  222.         st->stSize = size;
  223.         st->stColor.red = st->stColor.green = st->stColor.blue = 0;
  224.     }
  225.     
  226.     st->stCount += 1;
  227.     
  228.     if((*sh)->nRuns > 0 && (*sh)->runs[(*sh)->nRuns-1].startChar == start){
  229.         /* overwrite previous run, since it starts in the same place */
  230.         run = (*sh)->nRuns - 1;
  231.         st1 = (*((*sh)->styleTab)) + (*sh)->runs[run].styleIndex;
  232.         st1->stCount -= 1;
  233.     } else {
  234.         run = (*sh)->nRuns;
  235.         (*sh)->nRuns += 1;
  236.         newlen = (char *) &((*sh)->runs[(*sh)->nRuns]) - (char *) *sh;
  237.         SetHandleSize((Handle)sh, newlen);
  238.         if((err = MemError()) != 0){
  239.             (*sh)->nRuns -= 1;
  240.             error("error : out of memory for more runs");
  241.             return(err);
  242.         }
  243.     }
  244.     
  245.     (*sh)->runs[run].startChar = start;
  246.     (*sh)->runs[run].styleIndex = si;
  247.     
  248.     return(0);
  249. }
  250.  
  251. /*
  252.  * a '\\' has been read; read the control word and any argument.
  253.  * if the following delimiter is a space, consume it; otherwise
  254.  * leave it alone.
  255.  */
  256. void
  257. readcontrol(lp, word, arg)
  258. struct list *lp;
  259. char word[], arg[];    /* out parameters: the control word and argument */
  260. {
  261.     int i, c;
  262.     
  263.     word[0] = arg[0] = '\0';
  264.     
  265.     i = 0;
  266.     while(i < 254 && isalpha(c = ReadListChar(lp)))
  267.         word[i++] = c;
  268.     word[i] = '\0';
  269.     
  270.     if(i == 0){
  271.         switch(c){
  272.         case '\'':
  273.             /* \' followed by two hex digits */
  274.             word[0] = '\'';
  275.             word[1] = '\0';
  276.             arg[0] = ReadListChar(lp);
  277.             arg[1] = ReadListChar(lp);
  278.             arg[2] = '\0';
  279.             return;
  280.         default:
  281.             /* some one-character punctuation code */
  282.             word[0] = c;
  283.             word[1] = '\0';
  284.             return;
  285.         }
  286.     }
  287.     
  288.     if(isdigit(c) || c == '-'){
  289.         /* we have a parameter */
  290.         i = 0;
  291.         do{
  292.             arg[i++] = c;
  293.         } while(i < 254 && isdigit(c = ReadListChar(lp)));
  294.         arg[i] = '\0';
  295.     }
  296.     
  297.     if(c != ' ' && c != '\n' && c != '\r'){
  298.         /* give back the look-ahead character */
  299.         lp->ptr -= 1;
  300.     }
  301. }
  302.  
  303. State *
  304. pushstate(old)
  305. State *old;
  306. {
  307.     State *new;
  308.     
  309.     new = (State *) NewPtr(sizeof(State));
  310.     if(new == 0){
  311.         while(old){
  312.             new = old->last;
  313.             DisposPtr((Ptr)old);
  314.             old = new;
  315.         }
  316.         return(0);
  317.     }
  318.     
  319.     if(old){
  320.         *new = *old;
  321.     } else {
  322.         new->font = new->face = 0;
  323.         new->size = 12;
  324.         new->dirty = 1;
  325.         new->isfonttable = 0;
  326.         new->invisible = 0;
  327.         new->caps = 0;
  328.         new->deffont = -1;
  329.     }
  330.     
  331.     new->last = old;
  332.     return(new);
  333. }
  334.  
  335. State *
  336. popstate(st)
  337. State *st;
  338. {
  339.     State *last;
  340.     
  341.     if(st){
  342.         last = st->last;
  343.         DisposPtr((Ptr)st);
  344.         return(last);
  345.     }
  346.     return(0);
  347. }
  348.  
  349. void
  350. docontrol(inp, outp, state, sh, control, arg)
  351. struct list *inp, *outp;
  352. State *state;
  353. MyStyleHandle sh;
  354. char control[], arg[];
  355. {
  356.     int c;
  357.     
  358.     if(strcmp(control, "f") == 0){
  359.         if(state->isfonttable){
  360.             int mac, rtf, i, nfonts;
  361.             char family[256], name[256];
  362.     
  363.             rtf = atoi(arg);
  364.             
  365.             c = ReadListChar(inp);
  366.             while(isspace(c))
  367.                 c = ReadListChar(inp);
  368.             if(c == '\\'){
  369.                 readcontrol(inp, family, name);    /* read the family */
  370.             } else {
  371.                 inp->ptr -= 1;
  372.                 family[0] = '\0';
  373.             }
  374.             
  375.             /* read the font name, up to a semicolon */
  376.             i = 0;
  377.             while(i < 254 && (c = ReadListChar(inp)) != -1 && c != 0 &&
  378.                   c != ';' && c != '}' && c != '\\'){
  379.                 if(i == 0 && isspace(c))
  380.                     continue;
  381.                 name[i++] = c;
  382.             }
  383.             name[i] = '\0';
  384.             CtoPstr(name);
  385.             GetFNum((StringPtr)name, &mac);
  386.             nfonts = GetHandleSize((Handle) ftable) / sizeof(**ftable);
  387.             SetHandleSize((Handle) ftable, sizeof(**ftable) * (nfonts + 1));
  388.             if(MemError() == 0){
  389.                 (*ftable)[nfonts].rtf = rtf;
  390.                 (*ftable)[nfonts].mac = mac;
  391.             } else
  392.                 error("error : out of memory for more ftable");
  393.         } else {
  394.             state->font = findrtffont(atoi(arg));
  395.             state->dirty = 1;
  396.         }
  397.     } else if(strcmp(control, "deff") == 0){
  398.         state->deffont = atoi(arg);
  399.     } else if(strcmp(control, "plain") == 0){
  400.         state->font = findrtffont(state->deffont);
  401.         state->face = 0;
  402.         state->size = 12;    /* ??? */
  403.         state->dirty = 1;
  404.         state->invisible = 0;
  405.         state->caps = 0;
  406.     } else if(strcmp(control, "b") == 0){
  407.         xface(state, bold, arg);
  408.     } else if(strcmp(control, "i") == 0){
  409.         xface(state, italic, arg);
  410.     } else if(strcmp(control, "outl") == 0){
  411.         xface(state, outline, arg);
  412.     } else if(strcmp(control, "shad") == 0){
  413.         xface(state, shadow, arg);
  414.     } else if(strcmp(control, "hcgroup") == 0){
  415.         xface(state, 0x80, arg);    /* HyperCard grouped text */
  416.     } else if(strcmp(control, "v") == 0){
  417.         if(arg[0] == '0')
  418.             state->invisible = 0;
  419.         else
  420.             state->invisible = 1;
  421.     } else if(strcmp(control, "fs") == 0){
  422.         state->size = atoi(arg) / 2;
  423.         state->dirty = 1;
  424.     } else if(strcmp(control, "ul") == 0 || strcmp(control, "ulw") == 0 ||
  425.               strcmp(control, "uld") == 0 || strcmp(control, "uldb") == 0){
  426.         xface(state, underline, arg);
  427.     } else if(strcmp(control, "ulnone") == 0){
  428.         state->face &= ~underline;
  429.         state->dirty = 1;
  430.     } else if(strcmp(control, "~") == 0){
  431.         appchars(outp, "\xCA", state, sh); /* option-space */
  432.     } else if(strcmp(control, "_") == 0){
  433.         appchars(outp, "-", state, sh);
  434.     } else if(strcmp(control, "bullet") == 0){
  435.         appchars(outp, "•", state, sh); /* option-8 */
  436.     } else if(strcmp(control, "ldblquote") == 0){
  437.         appchars(outp, "“", state, sh); /* option-[ */
  438.     } else if(strcmp(control, "rdblquote") == 0){
  439.         appchars(outp, "”", state, sh); /* option-shift-[ */
  440.     } else if(strcmp(control, "endash") == 0){
  441.         appchars(outp, "–", state, sh); /* option-- */
  442.     } else if(strcmp(control, "emdash") == 0){
  443.         appchars(outp, "—", state, sh); /* option-shift-- */
  444.     } else if(strcmp(control, "lquote") == 0){
  445.         appchars(outp, "‘", state, sh); /* option-] */
  446.     } else if(strcmp(control, "rquote") == 0){
  447.         appchars(outp, "’", state, sh); /* option-shift-] */
  448.     } else if(strcmp(control, "'") == 0){
  449.         char bf[2];
  450.         sscanf(arg, "%x", &c);
  451.         bf[0] = c;
  452.         bf[1] = '\0';
  453.         appchars(outp, bf, state, sh);
  454.     } else if(strcmp(control, "line") == 0){
  455.         appchars(outp, "\r", state, sh);
  456.     } else if(strcmp(control, "tab") == 0){
  457.         appchars(outp, "    ", state, sh);
  458.     } else if(strcmp(control, "colortbl") == 0 ||
  459.               strcmp(control, "pict") == 0 ||
  460.               strcmp(control, "footnote") == 0 ||
  461.               strcmp(control, "annotation") == 0 ||
  462.               strcmp(control, "header") == 0 ||
  463.               strcmp(control, "footer") == 0 ||
  464.               strcmp(control, "info") == 0 ||
  465.               strcmp(control, "field") == 0 ||
  466.               strcmp(control, "*") == 0){
  467.         flushgroup(inp);
  468.     } else if(strcmp(control, "stylesheet") == 0){
  469.         flushgroup(inp);    /* seems to be OK to ignore */
  470.     } else if(strcmp(control, "par") == 0 || strcmp(control, "page") == 0){
  471.         appchars(outp, "\r", state, sh);
  472.     } else if(strcmp(control, "caps") == 0){
  473.         state->caps = (arg[0] == '0' ? 0 : 1);
  474.     } else if(isalnum(control[0]) == 0){
  475.         appchars(outp, control, state, sh);
  476.     } else if(strcmp(control, "fonttbl") == 0){
  477.         state->isfonttable = 1;
  478.         SetHandleSize((Handle)ftable, 0L);
  479.     }
  480. }
  481.  
  482. void
  483. appchars(outp, s, state, sh)
  484. struct list *outp;
  485. char *s;
  486. State *state;
  487. MyStyleHandle sh;
  488. {
  489.     if(state->invisible == 0){
  490.         if(state->dirty){
  491.             startstyle(sh, outp->ptr, state->font, state->face, state->size);
  492.             state->dirty = 0;
  493.         }
  494.         AppendList(outp, s);
  495.     }
  496. }
  497.         
  498.  
  499. int
  500. findrtffont(rtf)
  501. {
  502.     int i, nfonts;
  503.     
  504.     nfonts = GetHandleSize((Handle) ftable) / sizeof(**ftable);
  505.     for(i = 0; i < nfonts; i++){
  506.         if((*ftable)[i].rtf == rtf){
  507.             return((*ftable)[i].mac);
  508.         }
  509.     }
  510.     return(0);
  511. }
  512.  
  513. xface(state, face, arg)
  514. State *state;
  515. int face;
  516. char arg[];
  517. {
  518.     if(arg[0] == '0')
  519.         state->face &= ~face;
  520.     else
  521.         state->face |= face;
  522.     state->dirty = 1;
  523. }
  524.  
  525. flushgroup(inp)
  526. struct list *inp;
  527. {
  528.     int c, depth;
  529.     
  530.     depth = 0;
  531.     while((c = ReadListChar(inp)) != -1 && c != 0){
  532.         if(c == '}'){
  533.             if(depth <= 0){
  534.                 inp->ptr -= 1; /* allow } to pop the state */
  535.                 break;
  536.             }
  537.             --depth;
  538.         } else if(c == '{'){
  539.             depth++;
  540.         }
  541.     }
  542. }
  543.  
  544. MyStyleHandle
  545. NewMyStyleHandle(void)
  546. {
  547.     MyStyleHandle sh;
  548.     
  549.     sh = (MyStyleHandle) NewHandle(sizeof(**sh));
  550.     if(sh == 0)
  551.         return(0);
  552.         
  553.     (*sh)->nRuns = 0;
  554.     (*sh)->nStyles = 0;
  555.     (*sh)->styleTab = (STHandle) NewHandle(sizeof(STElement));
  556.     if((*sh)->styleTab == 0){
  557.         DisposHandle(sh);
  558.         return(0);
  559.     }
  560.     return(sh);
  561. }
  562.  
  563. void
  564. DisposMyStyleHandle(MyStyleHandle sh)
  565. {
  566.     if(sh){
  567.         if((*sh)->styleTab)
  568.             DisposHandle((*sh)->styleTab);
  569.         (*sh)->styleTab = 0;
  570.         DisposHandle(sh);
  571.     }
  572. }
  573.  
  574. /*
  575.  * convert one of my style handles into a TextEdit TEStyleHandle.
  576.  * since my style handles can deal with > 32K of text, *start
  577.  * indicates where to start converting. this routine leaves the
  578.  * first unconverted character position in *start when it returns.
  579.  * it tries not to split lines. it modifies an existing style handle.
  580.  * max specifies how many characters can be put in a field.
  581.  * txt is the total txt (ie might be > 32K).
  582.  * remember that (*sh)->nRuns doesn't count that last dummy run.
  583.  */
  584. OSErr
  585. CvtMyStyleHandle(MyStyleHandle sh1, long *start, TEStyleHandle sh2, long max,
  586.                  Handle txt)
  587. {
  588.     STHandle st;
  589.     long firstrun, lastrun, run, txtlen, len, i;
  590.     OSErr err;
  591.     
  592.     txtlen = GetHandleSize(txt);
  593.     if(*start + max > txtlen)
  594.         max = txtlen - *start;
  595.         
  596.     /* shrink max soas to break at a return */
  597.     if(*start + max < txtlen){
  598.         for(i = max-1 ; i >= 0; --i)
  599.             if((*txt)[*start + i] == '\r')
  600.                 break;
  601.         if(i > 0)
  602.             max = i + 1;
  603.     }
  604.     
  605.     /* find the first relevant run */
  606.     for(run = 0; run < (*sh1)->nRuns; run++)
  607.         if((*sh1)->runs[run].startChar <= *start &&
  608.            (*sh1)->runs[run + 1].startChar > *start)
  609.             break;
  610.     firstrun = run;
  611.             
  612.     /* find the last relevant run: it must be within max of *start */
  613.     for(run = firstrun; run <= (*sh1)->nRuns; run++)
  614.         if((*sh1)->runs[run].startChar >= *start + max)
  615.             break;
  616.     lastrun = run - 1;
  617.     
  618.     /*
  619.      * allocate enough space in sh2's tables.
  620.      * also copies sh1's styleTab. this might be a bit
  621.      * wasteful if any are unused, but it means that
  622.      * the same indices can be used in sh2 as in sh1.
  623.      */
  624.     (*sh2)->nRuns = lastrun - firstrun + 1;
  625.     (*sh2)->nStyles = (*sh1)->nStyles;
  626.     st = (*sh1)->styleTab;
  627.     HandToHand(&st);
  628.     if((*sh2)->styleTab)
  629.         DisposHandle((*sh2)->styleTab);
  630.     (*sh2)->styleTab = st;
  631.     SetHandleSize(sh2, 32L + ((*sh2)->nRuns + 1) * sizeof(StyleRun));
  632.     if((err = MemError()) != 0)
  633.         return(err);
  634.         
  635.     /* copy the relevant runs, adjusting the character pointers */
  636.     for(run = firstrun; run <= lastrun; run++){
  637.         (*sh2)->runs[run - firstrun].startChar = (*sh1)->runs[run].startChar - *start;
  638.         if((*sh2)->runs[run - firstrun].startChar < 0)
  639.             (*sh2)->runs[run - firstrun].startChar = 0;
  640.         (*sh2)->runs[run - firstrun].styleIndex = (*sh1)->runs[run].styleIndex;
  641.     }
  642.     
  643.     if(lastrun < (*sh1)->nRuns)
  644.         len = (*sh1)->runs[lastrun + 1].startChar - *start;
  645.     else
  646.         len = txtlen - *start;
  647.     if(len > max)
  648.         len = max;
  649.     (*sh2)->runs[(*sh2)->nRuns].startChar = len + 1;
  650.     (*sh2)->runs[(*sh2)->nRuns].styleIndex = -1;
  651.     *start += len;
  652.     
  653.     return(0);
  654. }